package com.java.eyes.ui;

import java.awt.BorderLayout;
import java.awt.Button;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.MediaTracker;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.ImageObserver;
import java.awt.image.MemoryImageSource;
import java.awt.image.PixelGrabber;

import com.java.eyes.processors.ImageProcessor;

public class SimpleView extends Frame {

	Image rawImg;
	int imgCols;// Number of horizontal pixels
	int imgRows;// Number of rows of pixels
	Image modImg;// Reference to modified image

	// Inset values for the Frame
	int inTop;
	int inLeft;


	MediaTracker tracker;
	Display display = new Display();// A Canvas
	Button replotButton = new Button("Replot");

	// References to arrays that store pixel data.
	int[][][] threeDPix;
	int[][][] threeDPixMod;
	int[] oneDPix;

	public SimpleView(String theImgFile, final ImageProcessor imageProcessingObject) {
		
		
		// Get an image from the specified file. Can
		// be in a different directory if the path
		// was entered with the file name on the
		// command line.
		rawImg = Toolkit.getDefaultToolkit().getImage(theImgFile);

		// Use a MediaTracker object to block until
		// the image is loaded or ten seconds has
		// elapsed.
		tracker = new MediaTracker(this);
		tracker.addImage(rawImg, 1);

		try {
			if (!tracker.waitForID(1, 10000)) {
				System.out.println("Load error.");
				System.exit(1);
			}// end if
		} catch (InterruptedException e) {
			e.printStackTrace();
			System.exit(1);
		}// end catch

		// Make certain that the file was successfully
		// loaded.
		if ((tracker.statusAll(false) & MediaTracker.ERRORED & MediaTracker.ABORTED) != 0) {
			System.out.println("Load errored or aborted");
			System.exit(1);
		}// end if

		// Raw image has been loaded. Get width and
		// height of the raw image.
		imgCols = rawImg.getWidth(this);
		imgRows = rawImg.getHeight(this);

		this.setTitle("Copyright 2004, Baldwin");
		this.setBackground(Color.YELLOW);
		this.add(display);
		this.add(replotButton, BorderLayout.SOUTH);
		// Make it possible to get insets and the
		// height of the button.
		setVisible(true);
		// Get and store inset data for the Frame and
		// the height of the button.
		inTop = this.getInsets().top;
		inLeft = this.getInsets().left;
		int buttonHeight = replotButton.getSize().height;
		// Size the frame so that a small amount of
		// yellow background will show on the right
		// and on the bottom when both images are
		// displayed, one above the other. Also, the
		// placement of the images on the Canvas
		// causes a small amount of background to
		// show between the images.
		this.setSize(2 * inLeft + imgCols + 1, inTop + buttonHeight + 2
				* imgRows + 7);

		// =========================================//
		// Anonymous inner class listener for replot
		// button. This actionPerformed method is
		// invoked when the user clicks the Replot
		// button. It is also invoked at startup
		// when this program posts an ActionEvent to
		// the system event queue attributing the
		// event to the Replot button.
		replotButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				// Pass a 3D array of pixel data to the
				// processing object and get a modified
				// 3D array of pixel data back. The
				// creation of the 3D array of pixel
				// data is explained later.
				threeDPixMod = imageProcessingObject.processImg(threeDPix,
						imgRows, imgCols);
				// Convert the modified pixel data to a
				// 1D array of pixel data. The 1D
				// array is explained later.
				oneDPix = convertToOneDim(threeDPixMod, imgCols, imgRows);
				// Use the createImage() method to
				// create a new image from the 1D array
				// of pixel data.
				modImg = createImage(new MemoryImageSource(imgCols, imgRows,
						oneDPix, 0, imgCols));
				// Repaint the image display frame with
				// the original image at the top and
				// the modified pixel data at the
				// bottom.
				display.repaint();
			}// end actionPerformed
		}// end ActionListener
				);// end addActionListener
		// End anonymous inner class.
		// =========================================//

		// Create a 1D array object to receive the
		// pixel representation of the image
		oneDPix = new int[imgCols * imgRows];

		// Convert the rawImg to numeric pixel
		// representation. Note that grapPixels()
		// throws InterruptedException
		try {
			// Instantiate a PixelGrabber object
			// specifying oneDPix as the array in which
			// to put the numeric pixel data. See Sun
			// docs for parameters
			PixelGrabber pgObj = new PixelGrabber(rawImg, 0, 0, imgCols,
					imgRows, oneDPix, 0, imgCols);
			// Invoke the grabPixels() method on the
			// PixelGrabber object to extract the pixel
			// data from the image into an array of
			// numeric pixel data stored in oneDPix.
			// Also test for success in the process.
			if (pgObj.grabPixels()
					&& ((pgObj.getStatus() & ImageObserver.ALLBITS) != 0)) {

				// Convert the pixel byte data in the 1D
				// array to int data in a 3D array to
				// make it easier to work with the pixel
				// data later. Recall that pixel data is
				// unsigned byte data and Java does not
				// support unsigned arithmetic.
				// Performing unsigned arithmetic on byte
				// data is particularly cumbersome.
				threeDPix = convertToThreeDim(oneDPix, imgCols, imgRows);

				// Instantiate a new object of the image
				// processing class. Note that this
				// object is instantiated using the
				// newInstance method of the class named
				// Class. This approach does not support
				// the use of a parameterized
				// constructor.
				try {
					

					// Post counterfeit ActionEvent to the
					// system event queue and attribute it
					// to the Replot button. (See the
					// anonymous ActionListener class
					// defined above that registers an
					// ActionListener object on the RePlot
					// button.) Posting this event causes
					// the image-processing method to be
					// invoked, passing the 3D array of
					// pixel data to the method, and
					// receiving a 3D array of modified
					// pixel data back from the method.
					Toolkit.getDefaultToolkit().getSystemEventQueue()
							.postEvent(
									new ActionEvent(replotButton,
											ActionEvent.ACTION_PERFORMED,
											"Replot"));

					// At this point, the image has been
					// processed and both the original
					// image and the modified image
					// have been displayed. From this
					// point forward, each time the user
					// clicks the Replot button, the image
					// will be processed again and the new
					// modified image will be displayed
					// along with the original image.

				} catch (Exception e) {
					System.out.println(e);
				}// end catch

			}// end if statement on grabPixels
			else
				System.out.println("Pixel grab not successful");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}// end catch

		// Cause the composite of the frame, the
		// canvas, and the button to become visible.
		this.setVisible(true);
		// =========================================//

		// Anonymous inner class listener to terminate
		// program.
		this.addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent e) {
				System.exit(0);// terminate the program
			}// end windowClosing()
		}// end WindowAdapter
				);// end addWindowListener
		// =========================================//

	}// end constructor

	// ===========================================//

	// Inner class for canvas object on which to
	// display the two images.
	class Display extends Canvas {
		// Override the paint method to display both
		// the rawImg and the modImg on the same
		// Canvas object, separated by one row of
		// pixels in the background color.
		public void paint(Graphics g) {
			// First confirm that the image has been
			// completely loaded and neither image
			// reference is null.
			if (tracker.statusID(1, false) == MediaTracker.COMPLETE) {
				if ((rawImg != null) && (modImg != null)) {
					g.drawImage(rawImg, 0, 0, this);
					g.drawImage(modImg, 0, imgRows + 1, this);
				}// end if
			}// end if
		}// end paint()
	}// end class myCanvas

	// =============================================//

	// Save pixel values as type int to make
	// arithmetic easier later.

	// The purpose of this method is to convert the
	// data in the int oneDPix array into a 3D
	// array of ints.
	// The dimensions of the 3D array are row,
	// col, and color in that order.
	// Row and col correspond to the rows and
	// columns of the image. Color corresponds to
	// transparency and color information at the
	// following index levels in the third
	// dimension:
	// 0 alpha
	// 1 red
	// 2 green
	// 3 blue
	// The structure of this code is determined by
	// the way that the pixel data is formatted
	// into the 1D array of ints produced by the
	// grabPixels method of the PixelGrabber
	// object.
	int[][][] convertToThreeDim(int[] oneDPix, int imgCols, int imgRows) {
		// Create the new 3D array to be populated
		// with color data.
		int[][][] data = new int[imgRows][imgCols][4];

		for (int row = 0; row < imgRows; row++) {
			// Extract a row of pixel data into a
			// temporary array of ints
			int[] aRow = new int[imgCols];
			for (int col = 0; col < imgCols; col++) {
				int element = row * imgCols + col;
				aRow[col] = oneDPix[element];
			}// end for loop on col

			// Move the data into the 3D array. Note
			// the use of bitwise AND and bitwise right
			// shift operations to mask all but the
			// correct set of eight bits.
			for (int col = 0; col < imgCols; col++) {
				// Alpha data
				data[row][col][0] = (aRow[col] >> 24) & 0xFF;
				// Red data
				data[row][col][1] = (aRow[col] >> 16) & 0xFF;
				// Green data
				data[row][col][2] = (aRow[col] >> 8) & 0xFF;
				// Blue data
				data[row][col][3] = (aRow[col]) & 0xFF;
			}// end for loop on col
		}// end for loop on row
		return data;
	}// end convertToThreeDim

	// -------------------------------------------//

	// The purpose of this method is to convert the
	// data in the 3D array of ints back into the
	// 1d array of type int. This is the reverse
	// of the method named convertToThreeDim.
	int[] convertToOneDim(int[][][] data, int imgCols, int imgRows) {
		// Create the 1D array of type int to be
		// populated with pixel data, one int value
		// per pixel, with four color and alpha bytes
		// per int value.
		int[] oneDPix = new int[imgCols * imgRows * 4];

		// Move the data into the 1D array. Note the
		// use of the bitwise OR operator and the
		// bitwise left-shift operators to put the
		// four 8-bit bytes into each int.
		for (int row = 0, cnt = 0; row < imgRows; row++) {
			for (int col = 0; col < imgCols; col++) {
				oneDPix[cnt] = ((data[row][col][0] << 24) & 0xFF000000)
						| ((data[row][col][1] << 16) & 0x00FF0000)
						| ((data[row][col][2] << 8) & 0x0000FF00)
						| ((data[row][col][3]) & 0x000000FF);
				cnt++;
			}// end for loop on col

		}// end for loop on row

		return oneDPix;
	}// end convertToOneDim
}
